/* 
 * Copyright (c) 2012 JSquirrelEngine
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in 
 * the Software without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 */

package org.jsquirrel.geometry.impl;

import org.jsquirrel.geometry.BoundingArea;
import org.jsquirrel.geometry.BoundingAreaType;
import org.jsquirrel.geometry.Polygon;
import org.jsquirrel.geometry.UnsupportedBoundingIntersectionException;
import org.jsquirrel.geometry.VertexIterator;
import org.jsquirrel.math.F2Vector;
import org.jsquirrel.math.FInterval;


/**
 * Implementation of class BoundingArea, the area is represented as a circle which completely
 * contains a given Polygon
 * 
 * @author Juan David Adarve Bermudez
 *
 */
public class BoundingCircle extends BoundingArea {
	
	//*********************************************************************************************
	// INSTANCE ATTRIBUTES
	//*********************************************************************************************
	
	float radius;
	F2Vector center;
	
	
	//*********************************************************************************************
	// CONSTRUCTORS
	//*********************************************************************************************
	
	public BoundingCircle() {
		super(BoundingAreaType.BOUNDING_CIRCLE);
		radius = 0.0f;
		center = new F2Vector(0, 0);
	}
	
	
	//*********************************************************************************************
	// PUBLIC METHODS
	//*********************************************************************************************
	
	@Override
	public boolean intersects(BoundingArea area) throws UnsupportedBoundingIntersectionException{
		
		switch(area.type()) {
			case BOUNDING_CIRCLE:
				return circleIntersection((BoundingCircle)area);
				
			case BOUNDING_RECTANGLE:
				return rectangleIntersection((BoundingRectangle)area);
		}
		
		throw new UnsupportedBoundingIntersectionException("BoundingCircle.intersects(): Unknown BoundingArea type: " + area.type());
	}
	
	@Override
	public boolean intersectsRectangle(float xLeft, float yLeft, float width,
			float height) throws UnsupportedBoundingIntersectionException {
		
		// half the width and height of the rectangle
		float wh, hh;
		wh = width/2;
		hh = height/2;
		
		// coordinates for the center of the rectangle
		float xc, yc;
		xc = xLeft + wh;
		yc = yLeft + hh;
		
		
		/*
		 * NOTE: By including the two comparison of the center coordinates with the two X and Y axis,
		 * the execution time for the intersection when it is inside the Voronoi Area is constant
		 * with some overhead, though. 
		 */		
		// if the center of the circle is inside the Voronoi Area of the rectangle
		if(FInterval.pointInside(xLeft, xLeft + width, center.x()) ||
				FInterval.pointInside(yLeft, yLeft + height, center.y())) {
		
			return Math.abs(center.x() - xc) <= radius + wh ||
				Math.abs(center.y() - yc) <= radius + hh;
			
		}
		// outside of the Voronoi Area...
		else {
			
			// distance from the center to one corner of the rectangle
			float hyp = (float)Math.sqrt(wh*wh + hh*hh);
			
			return F2Vector.distanceBetween(xc, yc, center.x(), center.y()) <= radius + hyp;
		}
	}
	

	@Override
	public void getBoundingRectangle(F2Vector bLeftCorner, F2Vector uRightCorner) {
		
		bLeftCorner.setX(center.x() - radius);
		bLeftCorner.setY(center.y() - radius);
		
		uRightCorner.setX(center.x() + radius);
		uRightCorner.setY(center.y() + radius);
	}
	
	
	@Override
	public void update(Polygon p) {
		
		if(p.length() > 0) {
			
			radius = 0;
			VertexIterator it = p.getIterator();
			
			float cx = 0;
			float cy = 0;
			
			while(it.hasNext()) {
				
				F2Vector vertex = it.next();
				
				cx += vertex.x;
				cy += vertex.y;				
			}
			
			
			// center of mass of the vertex
			cx = cx / p.length();
			cy = cy / p.length();
			
			center.setX(cx);
			center.setY(cy);
			
			
			// RADIUS CALCULATION
			
			this.radius = 0;
			
			it = null;
			it = p.getIterator();			
			while(it.hasNext()) {
				
				F2Vector vertex = it.next();
				
				float distance = F2Vector.distanceBetween(vertex.x, vertex.y, center.x, center.y);
				if(distance >= radius) {
					radius = distance;
				}
			}
		}
	}
	
	@Override
	public String toString() {
		return "bc={(" + center.x() + ", " + center.y() + "), r=" + radius +"}";
	}
	
	
	/**
	 * Returns the radius of this bounding circle.
	 * 
	 * @return bounding circle radius
	 */
	public float radius() {
		return radius;
	}
	
	/**
	 * Returns the X coordinate of the circle center.
	 * 
	 * @return X coordinate of the circle center
	 */
	public float x() {
		return center.x();
	}
	
	/**
	 * Returns the Y coordinate of the circle center
	 * 
	 * @return Y coordinate of the circle center.
	 */
	public float y() {
		return center.y();
	}
	
	//*********************************************************************************************
	// PRIVATE METHODS
	//*********************************************************************************************
	
	/**
	 * Checks intersection between this and another BoundingCircle.
	 * 
	 * @param circle BoundingCircle instance.
	 * 
	 * @return true in case of intersections, false otherwise
	 */
	private boolean circleIntersection(BoundingCircle circle) {
		
		return F2Vector.distanceBetween(this.center, circle.center) <= this.radius + circle.radius;
	}
	
	/**
	 * Checks intersection between this circle and a BoundingRectangle instance
	 * 
	 * @param rectangle BoundingRectangle instance
	 * 
	 * @return true in case of intersection, false otherwise
	 */
	
	private boolean rectangleIntersection(BoundingRectangle rectangle) {
		
		// half the width and height of the rectangle
		float wh, hh;
		wh = (rectangle.xRight - rectangle.xLeft)/2;
		hh = (rectangle.yRight - rectangle.yLeft)/2;
		
		// coordinates for the center of the rectangle
		float xc, yc;
		xc = rectangle.xLeft + wh;
		yc = rectangle.yLeft + hh;
		
		
		/*
		 * NOTE: By including the two comparison of the center coordinates with the two X and Y axis,
		 * the execution time for the intersection when it is inside the Boronoi Area is constant
		 * with some overhead, though. 
		 */		
		// if the center of the circle is inside the Voronoi Area of the rectangle
		if(FInterval.pointInside(rectangle.xLeft, rectangle.xRight, center.x()) ||
				FInterval.pointInside(rectangle.yLeft, rectangle.yRight, center.y())) {
		
			return Math.abs(center.x() - xc) <= radius + wh ||
				Math.abs(center.y() - yc) <= radius + hh;
			
		}
		// outside of the Voronoi Area...
		else {
			
			// distance from the center to one corner of the rectangle
			float hyp = (float)Math.sqrt(wh*wh + hh*hh);
			
			return F2Vector.distanceBetween(xc, yc, center.x(), center.y()) <= radius + hyp;
		}
	}

}
